export const colors = [
    {
        code: 1001,
        describe: "1号杆",
        cname: "黑色",
        name: "Black",
        hex: "#000000",
    },
    {
        code: 1002,
        describe: "2号杆",
        cname: "银灰色",
        name: "Silver Gray",
        hex: "#abb0b2",
    },
    {
        code: 1003,
        describe: "3号杆",
        cname: "白色",
        name: "White",
        hex: "#ffffff",
    },
    {
        code: 1004,
        describe: "4号杆",
        cname: "老金色",
        name: "Old Gold",
        hex: "#95704e",
    },
    {
        code: 1005,
        describe: "5号杆",
        cname: "黄色",
        name: "Yellow",
        hex: "#ffcc00",
    },
    {
        code: 1006,
        describe: "6号杆",
        cname: "橙色",
        name: "Orange",
        hex: "#fa5716",
    },
    {
        code: 1007,
        describe: "7号杆",
        cname: "火烈鸟",
        name: "Flamingo",
        hex: "#f0297f",
    },
    {
        code: 1008,
        describe: "8号杆",
        cname: "海军蓝",
        name: "Navy",
        hex: "#131f4f",
    },
    {
        code: 1009,
        describe: "9号杆",
        cname: "草绿",
        name: "Lime Green",
        hex: "#78be3f",
    },
    {
        code: 1010,
        describe: "10号杆",
        cname: "绿色",
        name: "Kelly Green",
        hex: "#007848",
    },
    {
        code: 1011,
        describe: "11号杆",
        cname: "蓝色",
        name: "Royal Blue",
        hex: "#005397",
    },
    {
        code: 1012,
        describe: "12号杆",
        cname: "水蓝",
        name: "Aqua",
        hex: "#00a8e0",
    },
    {
        code: 1013,
        describe: "13号杆",
        cname: "红褐色",
        name: "Maroon",
        hex: "#670001",
    },
    {
        code: 1014,
        describe: "14号杆",
        cname: "红色",
        name: "Red",
        hex: "#b82828",
    },
    {
        code: 1015,
        describe: "15号杆",
        cname: "紫色",
        name: "Purple",
        hex: "#5f388c",
    },
];

const encode_thread_change = (command, thread, needle, order) => {
    if (Number.isNaN(thread)) {
        thread = 0;
    } else {
        thread &= 0xff;
        thread += 1;
    }

    if (Number.isNaN(needle)) {
        needle = 0;
    } else {
        needle &= 0xff;
        needle += 1;
    }

    if (Number.isNaN(order)) {
        order = 0;
    } else {
        order &= 0xff;
        order += 1;
    }

    command &= TYPES.COMMAND_MASK;
    return command | (order << 24) | (needle << 16) | (thread << 8);
};

export const TYPES = {
    // The commands below 0xFF are intended to denote proper commands.
    // The encodings beyond this is supplying additional information.

    COMMAND_MASK: 0x000000ff,
    THREAD_MASK: 0x0000ff00,
    NEEDLE_MASK: 0x00ff0000,
    ORDER_MASK: 0xff000000,

    FLAGS_MASK: 0x0000ff00,

    NO_COMMAND: -1,
    STITCH: 0,
    JUMP: 1,
    TRIM: 2,
    STOP: 3,
    END: 4,
    COLOR_CHANGE: 5,
    NEEDLE_SET: 9,
    SEQUIN_MODE: 6,
    SEQUIN_EJECT: 7,
    SLOW: 0xb, // 11
    FAST: 0xc, // 12

    // Assigns the change sequences, allowing the color change commands to trigger any event.
    // For these can be processed in some needle, thread, order values to preset or postset the
    // Underlying change sequence.
    SET_CHANGE_SEQUENCE: 0x10, // 16

    // Stitch with implied contingency.
    SEW_TO: 0xb0, // 176
    NEEDLE_AT: 0xb1, // 177

    STITCH_BREAK: 0xe0, // 224

    SEQUENCE_BREAK: 0xe1, // 225
    COLOR_BREAK: 0xe2, // 226

    // Middle Level commands.
    TIE_ON: 0xe4, // 228
    TIE_OFF: 0xe5, // 229
    FRAME_EJECT: 0xe9, // 233

    // Matrix Commands.
    MATRIX_TRANSLATE: 0xc0, // 192
    MATRIX_SCALE_ORIGIN: 0xc1, // 193
    MATRIX_ROTATE_ORIGIN: 0xc2, // 194
    MATRIX_SCALE: 0xc4, // 196
    MATRIX_ROTATE: 0xc5, // 197
    MATRIX_RESET: 0xc3, // 195

    OPTION_MAX_STITCH_LENGTH: 0xd5,
    OPTION_MAX_JUMP_LENGTH: 0xd6,
    OPTION_EXPLICIT_TRIM: 0xd7,
    OPTION_IMPLICIT_TRIM: 0xd8,

    CONTINGENCY_TIE_ON_NONE: 0xd3,
    CONTINGENCY_TIE_ON_THREE_SMALL: 0xd1,

    CONTINGENCY_TIE_OFF_NONE: 0xd4,
    CONTINGENCY_TIE_OFF_THREE_SMALL: 0xd2,

    CONTINGENCY_LONG_STITCH_NONE: 0xf0,
    CONTINGENCY_LONG_STITCH_JUMP_NEEDLE: 0xf1,
    CONTINGENCY_LONG_STITCH_SEW_TO: 0xf2,

    CONTINGENCY_SEQUIN_UTILIZE: 0xf5,
    CONTINGENCY_SEQUIN_JUMP: 0xf6,
    CONTINGENCY_SEQUIN_STITCH: 0xf7,
    CONTINGENCY_SEQUIN_REMOVE: 0xf8,

    ALTERNATIVE: 0x100, // Generic flag for an alternative form.
};

export class Pattern {
    constructor(stitches = [], colors = []) {
        this.threads = [];
        this.colors = colors.slice(0);
        this.stitches = stitches.slice(0);
        this.extras = {}; // type: dict
    }

    // 移动
    move(dx = 0, dy = 0) {
        this.addStitchRelative(TYPES.JUMP, dx, dy);
    }
    // 刺绣
    stitch(dx = 0, dy = 0) {
        this.addStitchRelative(TYPES.STITCH, dx, dy);
    }

    // 结束
    end(dx = 0, dy = 0) {
        this.addStitchRelative(TYPES.END, dx, dy);
    }

    // 停针
    stop(dx = 0, dy = 0) {
        this.addStitchRelative(TYPES.STOP, dx, dy);
    }

    // 剪线
    trim(dx = 0, dy = 0) {
        this.addStitchRelative(TYPES.TRIM, dx, dy);
    }

    // 换色
    colorChange(dx = 0, dy = 0) {
        this.addStitchRelative(TYPES.COLOR_CHANGE, dx, dy);
    }

    // 换针
    needleChange(needle = 0, dx = 0, dy = 0) {
        const cmd = encode_thread_change(TYPES.NEEDLE_SET, undefined, needle);
        this.addStitchRelative(cmd, dx, dy);
    }

    // 亮片
    sequinMode(dx = 0, dy = 0) {
        this.addStitchRelative(TYPES.SEQUIN_MODE, dx, dy);
    }
    // 放亮片
    sequinEject(dx = 0, dy = 0) {
        this.addStitchRelative(TYPES.SEQUIN_EJECT, dx, dy);
    }

    setThreads(threads) {
        this.threads = threads;
    }

    // 绝对位置
    addStitchAbsolute(cmd, x = 0, y = 0) {
        this.stitches.push([x, y, cmd]);
    }

    // 相对位置
    addStitchRelative(cmd, dx = 0, dy = 0) {
        const last = this.stitches[this.stitches.length - 1] || [0, 0];
        this.stitches.push([last[0] + dx, last[1] + dy, cmd]);
    }

    // 插入针迹
    insertStitchRelative(position, cmd, dx = 0, dy = 0) {
        // Insert a relative stitch into the pattern. The stitch is relative to the stitch before it.
        // If inserting at position 0, it's relative to 0,0. If appending, add is called, updating the positioning.

        if (position < 0) {
            // 向前运行
            position += this.stitches.length; // I need positive positions.
        }

        if (position == 0) {
            this.stitches.unshift([dx, dy, cmd]); // started (0,0)
        } else if (Number.isNaN(position) || position == this.stitches.length) {
            this.addStitchRelative(cmd, dx, dy);
        } else if (0 < position && position < this.stitches.length) {
            const p = this.stitches[position - 1];
            const x = p[0] + dx;
            const y = p[1] + dy;
            this.stitches.splice(position, 0, [x, y, cmd]);
        }
    }

    // 垂直镜像
    invertPatternVertical() {
        this.stitches.map((stitch) => (stitch[1] = -stitch[1]));
    }

    // 添加颜色
    addColor(color) {
        this.colors.push(color);
    }

    // 设置颜色
    setColorsByCount(count) {
        // 默认不加白色
        const able = colors.filter((i) => i.name != "White");
        const list = new Array(count).fill(0);
        this.colors = list.map((s, i) =>
            Object.assign({}, able[i % able.length])
        );
    }

    // 偏移，要重置超始点
    setOffset(x, y) {
        // 按距离，拆分针数
        // 针数间距最大是121
        const distance = 120;
        // 新的针迹
        const stitches = this.stitches.slice(0);

        if (stitches.length > 0) {
            const first = stitches[0];
            const offsetX = first[0] + x;
            const offsetY = first[1] + y;
            const count = Math.ceil(
                Math.max(Math.abs(offsetX), Math.abs(offsetY)) / distance
            );
            const stepX = count > 0 ? Math.round(offsetX / count) : offsetX;
            const stepY = count > 0 ? Math.round(offsetY / count) : offsetY;

            // 清空
            this.stitches.length = 0;
            // 添加前缀
            for (let i = 0; i < count; i++) {
                this.addStitchAbsolute(TYPES.JUMP, i * stepX, i * stepY);
            }
            // 修改针迹
            stitches.map((stitche) =>
                this.addStitchAbsolute(
                    stitche[2],
                    stitche[0] + offsetX,
                    stitche[1] + offsetY
                )
            );
        }
    }

    // 获取容器大小
    getBounds() {
        if (this.stitches.length == 0) {
            return {
                left: 0,
                top: 0,
                right: 0,
                bottom: 0,
                width: 0,
                height: 0,
            };
        } else {
            const flags = new Map();
            const first = this.stitches[0];
            const len = this.stitches.length;
            const result = {
                left: first[0],
                top: first[1],
                right: first[0],
                bottom: first[1],
                width: 0,
                height: 0,
            };

            for (let i = 1; i < len; i += 1) {
                const stitch = this.stitches[i],
                    x = stitch[0],
                    y = stitch[1];

                result.left = result.left < x ? result.left : x;
                result.top = result.top < y ? result.top : y;
                result.right = result.right > x ? result.right : x;
                result.bottom = result.bottom > y ? result.bottom : y;

                flags.set(stitch[2], (flags.get(stitch[2]) || 0) + 1);
            }

            result.width = result.right - result.left;
            result.height = result.bottom - result.top;
            return result;
        }
    }

    // 按针迹提取块
    getAsStitchBlock() {
        if (this.stitches.length > 0) {
            let color = 0;
            let jump = 0;
            const blocks = [{ color: this.colors[color], stitches: [] }];

            this.stitches.map((stitch, index) => {
                const flags = stitch[2];
                const last = blocks[blocks.length - 1];

                if (
                    index == 0 &&
                    stitch[0] == 0 &&
                    stitch[1] == 0 &&
                    stitch[2] == 0
                ) {
                    // 第一个，是实针的不管
                } else if (flags == TYPES.STITCH) {
                    last.stitches.push(stitch);
                    jump > 0 && (jump = 0);
                } else if (flags == TYPES.JUMP) {
                    // 超过3个跳针就剪线线
                    jump += 1;
                    if (jump >= 3 && last.stitches.length > 0) {
                        jump = 0;
                        blocks.push({
                            color: this.colors[color % this.colors.length],
                            stitches: [],
                        });
                    }
                } else {
                    color = flags == TYPES.COLOR_CHANGE ? color + 1 : color;
                    jump = 0;
                    blocks.push({
                        color: this.colors[color % this.colors.length],
                        index: color,
                        stitches: [],
                    });
                }
            });

            return blocks.filter((i) => i.stitches.length > 0);
        } else {
            return [];
        }
    }

    getColorCodes() {
        return this.colors.map((i) => i.code).join(",");
    }
}
